package edu.caltech.android;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import edu.caltech.android.earthquake.Quake;

/**
 * Service for getting quake events from USGS via RSS feed. Periodically
 * refreshes. This is mostly taken from
 * "Professional Android 2 Application Development"
 * 
 * Portions of this code from Professional Android 2 Application Development,
 * ISBN: 978-0-470-56552-0, copyright Wiley Publishing Inc: 2010, by Reto Meier,
 * published under the Wrox imprint are used by permission of Wiley Publishing,
 * Inc. All rights reserved. This book and the Wrox code are available for
 * purchase or download at www.wrox.com
 * 
 * @author mfaulk
 * 
 */
public class EarthquakeService extends Service {
	private static final String TAG = "EarthquakeService";
	public static final String NEW_EARTHQUAKE_FOUND = "New_Earthquake_Found";
	
	private Timer updateTimer;
	// private float minimumMagnitude;

	private Handler handler = new Handler();

	@Override
	public void onCreate() {
		updateTimer = new Timer("earthquakeUpdates");
	}

	/**
	 * the Timer behavior should be moved to a background thread and triggered
	 * by Alarms
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		int updateFreq = 1; // minutes
		boolean autoUpdate = true;
		updateTimer.cancel();
		if (autoUpdate) {
			updateTimer = new Timer("earthquakeUpdates");
			updateTimer.scheduleAtFixedRate(new TimerTask() {
				public void run() {
					handler.post(new Runnable() {
						public void run() {
							Log.v(TAG, "doRefresh");
							refreshEarthquakes();
						}
					});
				}
			}, 0, updateFreq * 60 * 1000);
		} else
			refreshEarthquakes();
		return Service.START_STICKY;
	}

	private class EarthquakeLookupTask extends AsyncTask<Void, Quake, Void> {
		@Override
		protected Void doInBackground(Void... params) {

			// Get the XML, parse, and create Quakes.
			deleteOldQuakes();
			URL url;
			try {
				String quakeFeed = getString(R.string.quake_feed);
				url = new URL(quakeFeed);
				URLConnection connection;
				connection = url.openConnection();
				HttpURLConnection httpConnection = (HttpURLConnection) connection;
				int responseCode = httpConnection.getResponseCode();
				if (responseCode == HttpURLConnection.HTTP_OK) {
					InputStream in = httpConnection.getInputStream();
					DocumentBuilderFactory dbf = DocumentBuilderFactory
							.newInstance();
					DocumentBuilder db = dbf.newDocumentBuilder();
					// Parse the earthquake feed.
					Document dom = db.parse(in);
					Element docEle = dom.getDocumentElement();

					// Get a list of each earthquake entry.
					NodeList nl = docEle.getElementsByTagName("entry");
					if (nl != null && nl.getLength() > 0) {
						for (int i = 0; i < nl.getLength(); i++) {
							Element entry = (Element) nl.item(i);
							Element title = (Element) entry
									.getElementsByTagName("title").item(0);
							Element g = (Element) entry.getElementsByTagName(
									"georss:point").item(0);
							Element when = (Element) entry
									.getElementsByTagName("updated").item(0);
							Element link = (Element) entry
									.getElementsByTagName("link").item(0);

							String details = title.getFirstChild()
									.getNodeValue();
							String hostname = "http://earthquake.usgs.gov";
							String linkString = hostname
									+ link.getAttribute("href");
							String point = g.getFirstChild().getNodeValue();
							String dt = when.getFirstChild().getNodeValue();

							// date
							SimpleDateFormat sdf = new SimpleDateFormat(
									"yyyy-MM-dd'T'hh:mm:ss'Z'");
							Date qdate = new GregorianCalendar(0, 0, 0)
									.getTime();
							try {
								qdate = sdf.parse(dt);
							} catch (java.text.ParseException e) {
								e.printStackTrace();
							}

							// location
							String[] location = point.split(" ");
							Location l = new Location("dummyGPS");

							final double latitude = Double
									.parseDouble(location[0]);
							final double longitude = Double
									.parseDouble(location[1]);

							l.setLatitude(latitude);
							l.setLongitude(longitude);

							// magnitude
							String magnitudeString = details.split(" ")[1];
							int end = magnitudeString.length() - 1;
							final double magnitude = Double
									.parseDouble(magnitudeString.substring(0,
											end));

							// details
							details = details.split(",")[1].trim();
							Quake quake = new Quake(qdate, details, l,
									magnitude, linkString);

							// Process a newly found earthquake
							addNewQuake(quake);
							publishProgress(quake);
						}
					}

				}
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			} catch (SAXException e) {
				e.printStackTrace();
			} finally {
			}

			return null;
		}

		@Override
		protected void onProgressUpdate(Quake... values) {
			super.onProgressUpdate(values);
			// TODO could give a notification here about new quakes
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
		}
	}

	/**
	 * If this quake is new, add it to the database and announce its
	 * availability.
	 * 
	 * @param _quake
	 */
	private void addNewQuake(Quake _quake) {
		ContentResolver cr = getContentResolver();
		// Construct a where clause to make sure we don’t already have
		// this earthquake in the provider.
		String w = EarthquakeProvider.KEY_DATE + " = "
				+ _quake.getDate().getTime();
		// If the earthquake is new, insert it into the provider.
		Cursor c = cr
				.query(EarthquakeProvider.CONTENT_URI, null, w, null, null);
		if (c.getCount() == 0) {
			ContentValues values = new ContentValues();

			long t = _quake.getDate().getTime();
			// DateFormat dateFormat = new
			// SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
			// Log.v(TAG, "quake date:" + dateFormat.format(new Date(t)));

			values.put(EarthquakeProvider.KEY_DATE, t);
			values.put(EarthquakeProvider.KEY_DETAILS, _quake.getDetails());
			double lat = _quake.getLocation().getLatitude();
			double lng = _quake.getLocation().getLongitude();
			values.put(EarthquakeProvider.KEY_LOCATION_LAT, lat);
			values.put(EarthquakeProvider.KEY_LOCATION_LNG, lng);
			values.put(EarthquakeProvider.KEY_LINK, _quake.getLink());
			values.put(EarthquakeProvider.KEY_MAGNITUDE, _quake.getMagnitude());
			cr.insert(EarthquakeProvider.CONTENT_URI, values);
			announceNewQuake(_quake);
		}
		c.close();
	}

	/**
	 * Delete quakes that are more than 24 hours old.
	 */
	private void deleteOldQuakes() {
		//Log.v(TAG, "deleteOldQuakes");
		ContentResolver cr = getContentResolver();
		final long MILLIS_IN_A_DAY = 1000 * 60 * 60 * 24;
		Long yesterdayMillis = System.currentTimeMillis() - MILLIS_IN_A_DAY;
		String where = EarthquakeProvider.KEY_DATE + " < " + yesterdayMillis;
		@SuppressWarnings("unused")
		int nDeleted = cr.delete(EarthquakeProvider.CONTENT_URI, where, null);
		//Log.v(TAG, "Deleted " + nDeleted + " old quakes from database");
	}

	private void announceNewQuake(Quake quake) {
		//Log.v(TAG, "announceNewQuake");
		Intent intent = new Intent(NEW_EARTHQUAKE_FOUND);
		intent.putExtra("date", quake.getDate().getTime());
		intent.putExtra("details", quake.getDetails());
		intent.putExtra("longitude", quake.getLocation().getLongitude());
		intent.putExtra("latitude", quake.getLocation().getLatitude());
		intent.putExtra("magnitude", quake.getMagnitude());
		sendBroadcast(intent);
	}

	EarthquakeLookupTask lastLookup = null;

	/**
	 * Fetch quakes from RSS feed. Check if another asyncTask has begun to avoid
	 * running multiple tasks at the same time.
	 * 
	 * TODO I don't think that old earthquakes are ever removed from the
	 * database. Maybe here a delete task should be performed.
	 */
	private void refreshEarthquakes() {
		if (lastLookup == null
				|| lastLookup.getStatus().equals(AsyncTask.Status.FINISHED)) {
			lastLookup = new EarthquakeLookupTask();
			lastLookup.execute((Void[]) null);
		}
	}

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

}
